/*
 * Copyright 2007 Agile Plugins Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.agileplugins.monkey;

import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.IncorrectOperationException;

import javax.swing.*;
import java.util.ArrayList;
import java.util.List;

public class ConstantLookupAction extends AnAction {
    private static final String TWO = "two";
    public static final String ONE = "one" + TWO;
    public static final String TW = "tw";

    public void test() {
        String name1 = ConstantLookupAction.TWO;
        String name2 = ConstantLookupAction.TWO;
    }

    public void actionPerformed(AnActionEvent anActionEvent) {
        DataContext datacontext = anActionEvent.getDataContext();
        final Project project = DataKeys.PROJECT.getData(datacontext);
        if (project == null) {
            return;
        }
        PsiDocumentManager.getInstance(project).commitAllDocuments();
        final Editor editor = DataKeys.EDITOR.getData(datacontext);
        if (editor != null) {
            final PsiFile psifile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
            if (psifile == null) {
                return;
            }
            PsiElement element = getElement(editor, psifile);
            System.out.println("element = " + element);
            PsiField psiField = getField(element);
            if (psiField != null) {
                PsiModifierList modifierList = psiField.getModifierList();
                if (modifierList.hasModifierProperty(PsiModifier.STATIC) &&
                        modifierList.hasModifierProperty(PsiModifier.FINAL) &&
                        psiField.getType().getCanonicalText().equals(String.class.getName())) {
                    PsiExpression initializer = psiField.getInitializer();
                    if (initializer instanceof PsiLiteralExpression) {
                        PsiLiteralExpression psiLiteralExpression = (PsiLiteralExpression) initializer;
                        String literalExpression = (String) psiLiteralExpression.getValue();
                        lookupPossiblesOfConstant(psiField, project, psiLiteralExpression);
                        return;
                    }
                }
            }
            if (element instanceof PsiJavaToken) {
                PsiJavaToken psiJavaToken = (PsiJavaToken) element;
                if (psiJavaToken.getTokenType().equals(JavaTokenType.STRING_LITERAL)) {
                    try {
                        lookupConstant(project, editor, psifile, anActionEvent);
                        return;
                    } catch (IncorrectOperationException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            Messages.showErrorDialog("Can only lookup constants for string literals",
                    "Invalid Element");
        }
    }

    private PsiElement getElement(Editor editor, PsiFile psiFile) {
        int offset = editor.getCaretModel().getOffset();
        return psiFile.findElementAt(offset);
    }

    private PsiField getField(PsiElement psiElement) {
        while (!(psiElement instanceof PsiField) && psiElement != null) {
            psiElement = psiElement.getParent();
        }
        return (PsiField) psiElement;
    }

    private void lookupPossiblesOfConstant(PsiField psiField, Project project, PsiLiteralExpression psiLiteralExpression) {
        String literalExpression = (String) psiLiteralExpression.getValue();
        System.out.println("ConstantLookupAction.lookupPossiblesOfConstant");
        PsiClass containingClass = psiField.getContainingClass();
        final String expression = containingClass.getName() + "." + psiField.getName();
        final PsiManager manager = psiField.getManager();
        PsiLiteralExpression[] literals = manager.getSearchHelper().findStringLiteralsContainingIdentifier(literalExpression, GlobalSearchScope.allScope(project));
        final List<PsiLiteralExpression> list = new ArrayList<PsiLiteralExpression>();
        for (int i = 0; i < literals.length; i++) {
            PsiLiteralExpression literal = literals[i];
            if (literal != psiLiteralExpression && literal.getValue().equals(literalExpression)) {
                list.add(literal);
            }
        }
        if (list.size() == 0) {
            Messages.showInfoMessage("There are no strings found that could make use of this constant.", "Lookup Constant");
            return;
        } else {
            int i = Messages.showYesNoDialog(list.size() + " matches were found that could be replaced with this constant.  Would you like to replace them?", "Lookup Constant", UIManager.getIcon("OptionPane.questionIcon"));
            if (i != 0) {
                return;
            }
        }
        final Runnable runnable = new Runnable() {

            public void run() {
                try {
                    for (PsiLiteralExpression exp : list) {
                        final PsiExpression expressionFromText = manager.getElementFactory().createExpressionFromText(expression, exp);
                        exp.replace(expressionFromText);
                    }
                } catch (IncorrectOperationException e) {
                    e.printStackTrace();
                }
            }
        };
        CommandProcessor.getInstance().executeCommand(project,
                new Runnable() {
                    public void run() {
                        ApplicationManager.getApplication().runWriteAction(runnable);
                    }
                }, "Replace literal with constant", null);
    }

    private void lookupConstant(Project project, Editor editor, PsiFile psifile, AnActionEvent anActionEvent) throws IncorrectOperationException {
        String s = editor.getSelectionModel().getSelectedText();
        System.out.println("s = " + s);
        PsiElement element = getElement(editor, psifile);
        PsiJavaToken token = (PsiJavaToken) element;
        String text = token.getText();
        String trimtext = text.substring(1, text.length() - 1);
        PsiManager manager = element.getManager();
        PsiLiteralExpression[] literals = manager.getSearchHelper().findStringLiteralsContainingIdentifier(trimtext, GlobalSearchScope.allScope(project));
        boolean publicOptions = false;
        List options = new ArrayList();
        for (int i = 0; i < literals.length; i++) {
            PsiLiteralExpression literal = literals[i];
            if (!literal.getText().equals(text)) {
                continue;
            }
            PsiElement parent = literal.getParent();
            if (parent instanceof PsiField) {
                PsiField psiField = (PsiField) parent;
                PsiModifierList modifierList = psiField.getModifierList();
                if (modifierList.hasModifierProperty(PsiModifier.STATIC) &&
                        modifierList.hasModifierProperty(PsiModifier.FINAL)) {
                    if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
                        if (!publicOptions) {
                            publicOptions = true;
                            options.clear();
                        }
                    } else if (publicOptions || !psiField.isWritable()) {
                        continue;
                    }
                    PsiClass containingClass = psiField.getContainingClass();
                    String expression = containingClass.getName() + "." + psiField.getName();
                    options.add(expression);
                }
            }
        }
        String selectedOption;
        if (options.size() == 0) {
            int result = Messages.showYesNoDialog("Can't find an existing constant!  Would you like to introduce a constant?", "No Constant Found", UIManager.getIcon("OptionPane.questionIcon"));
            if (result == 0) { // Yes
                ActionManager amgr = ActionManager.getInstance();
                AnAction introduceConstantAction = amgr.getAction("IntroduceConstant");
                AnActionEvent newActionEvent = new AnActionEvent(anActionEvent.getInputEvent(),
                        DataManager.getInstance().getDataContext(),
                        anActionEvent.getPlace(),
                        anActionEvent.getPresentation(),
                        anActionEvent.getActionManager(), anActionEvent.getModifiers());
                introduceConstantAction.actionPerformed(newActionEvent);
            }
            return;
        }
        if (options.size() == 1) {
            selectedOption = (String) options.get(0);
            if (!publicOptions) {
                int result = Messages.showYesNoDialog("There are no public constants for the literal " + text + "\n but there was one private constant " + selectedOption + "\nWould you like to use this constant?", "No Public Constant", UIManager.getIcon("OptionPane.questionIcon"));
                if (result > 0) {
                    return;
                }
            }
        } else {
            String[] optionsArray = (String[]) options.toArray(new String[options.size()]);
            String message;
            if (publicOptions) {
                message = "There are multiple matches for the literal " + text + ".\nPlease choose a constant to use.";
            } else {
                message = "There are no public constants for the literal " + text + "\n but there are a number of non accessable constants.\nPlease choose a constant to use.";
            }
            MessageDialog dialog = new MessageDialog(
                    "Replace with Constant", message,
                    "refactoring.inlineParameter", "OptionPane.questionIcon", optionsArray, true, project, ListSelectionModel.SINGLE_SELECTION);
            dialog.show();
            if (!dialog.isOK()) {
                return;
            }
            selectedOption = dialog.getSelectedListOption();
        }
        final PsiExpression expressionFromText = manager.getElementFactory().createExpressionFromText(
                selectedOption, element);
        PsiElement thisParent = element.getParent();
        final PsiLiteralExpression literalExp = (PsiLiteralExpression) thisParent;
        final Runnable runnable = new Runnable() {

            public void run() {
                try {
                    literalExp.replace(expressionFromText);
                } catch (IncorrectOperationException e) {
                    e.printStackTrace();
                }
            }
        };
        CommandProcessor.getInstance().executeCommand(project,
                new Runnable() {
                    public void run() {
                        ApplicationManager.getApplication().runWriteAction(runnable);
                    }
                }, "Replace literal with constant", null);
    }
}
